import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import util.Config;

/**
 * Doraemon（哆啦A梦评语-分数）
 */
public class Doraemon {
    // 设置封装性成员变量
    private Map<String, List<String>> configMap;
    private String hwDir;
    private Random rnd;

    /**
     * 构造方法
     * 
     * @param configFile 用于指定配置文件，以供哆啦A梦了解评分细则
     * @param hwDir      用于指定作业存放目录
     */
    // 定义构造方法，传入评分细则和文件夹名，指定哆啦A梦使用的评分细则及存放作业的文件夹
    public Doraemon(String configFile, String hwDir) {
        // 通过getConfig()方法来获取Config里经过一系列处理的评分细则映射集
        this.configMap = Config.getConfig(configFile);
        // 指定作业存放的目录为hwDir
        this.hwDir = hwDir;
        // 使用单个种子创建新的随机数生成器,具有相同种子的对象生成的随机数序列相同
        //this.rnd = new Random(114514);
        this.rnd = new Random();
    }

    /**
     * 写评语
     * 
     * @param hwName 用于指定是给哪一次作业写评语<br>
     *               可以采用 `hw-1` 或者 `hw-{1:100}`的形式
     * @return 返回值是一个字典<br>
     *         键以 `hw-20210001-中野一花`的形式<br>
     *         值为评语字符串，其中包含了换行
     */
    // 创建一个返回值为key（hw-id-name）-value<评语>的映射集
    // 指定为哪一次作业写评语
    public Map<String, String> writeComment(String hwName) {
        // 声明一份个人作业根目录名单，将其赋值为在this.compileHwList(hwName)中通过一系列编译和匹配最终形成的个人作业目录
        List<String> hwNameList = this.compileHwList(hwName);
        // 将评语设置为一个key-value一一对应的映射集
        Map<String, String> comments = new HashMap<>();
        // 传入作业文件夹的地址
        File path = new File(hwDir);
        // 遍历作业名单列表(某个人的作业)中所有的本地作业名（这个人的某次作业）
        for (String localhwName : hwNameList) {
            // 获得作业文件夹中每份作业的域名地址并遍历
            for (File p : path.listFiles()) {
                // 返回抽象路径对应文件夹的名字，将其赋给表示“id-名字”的字符串
                String idAndName = p.getName();
                // 将键设置为：指定作业存放目录名、id和名字的组合、个人每次的作业名
                /**
                 * File.separator表示一种路径分隔符
                 */
                String key = this.hwDir + File.separator + idAndName + File.separator + localhwName;
                // 将值指定为：
                String value = this.generateContent();
                // 将键-值组合，添加进评语中
                System.out.println();
                comments.put(key, value);
            }
        }

        return comments;
    }

    /**
     * 生成评语
     * 
     * @return
     */
    // 定义一个返回评语字符串的方法
    private String generateContent() {
        /**
         * (1) this.configMap.get("not-late-ratio"):基于key:not-late-ratio
         * 获得其对应的值，根据在Config中具体的方法定义可知该值是一个list (2)
         * this.configMap.get("not-late-ratio").get(0):返回list中索引号为0的值，可知，为0.9 (3)
         * 总目标：找出迟交作业对应的权重，把这个概率作为种子给予radom生成随机数
         */
        // 判断固定随机数序列rnd和radio浮点数值的大小，若 rnd>ratio，条件成立，返回" "
        if (this.random(this.configMap.get("not-late-ratio").get(0)) == false) {
            return "";
        }
        // 创建一个可变的字符串实例，实例容器中的内容支持可变
        StringBuilder sb = new StringBuilder();
        // 将元素"base", "positive", "negative"保存在数组s中进行遍历
        for (String s : new String[] { "base", "positive", "negative" }) {
            // 返回key为"base-item-list""positive-item-list""negative-item-list"（不同评价等级的具体条目）所对应的所有值（即对不同作业层次中的具体要求），并将其存入baseItem中
            for (String baseItem : this.configMap.get(s + "-item-list")) {
                // 以“含有base/positive/negative-ratio”这个命名规范为指定键，获得键对应的所有值
                //// 判断固定随机数序列rnd和radio浮点数值的大小，若 rnd>ratio，条件成立，跳出当前循环
                if (this.random(this.configMap.get(s + "-ratio").get(0)) == false) {
                    continue;
                }
                // 返回评分细则key指定的base/positive/negative prefix命名下所对应的prefix标识符
                // 以字符串的形式存储base/positive/negative所指定的标识符和所有评语
                String line = this.configMap.get(s + "-prefix").get(0) + " " + baseItem;
                // 将评价等级、评价等级标识符和评语传入可变的字符串实例并换行
                sb.append(line + "\n");
            }
            // 遍历完一个等级的评分细则后都换一次行
            sb.append("\n");
        }
        // 打印出评分细则
        return sb.toString();
    }

    // 定义一个返回值为布尔类型的随机值方法，传入作业等级的权重
    private Boolean random(String ratio) {
        // 将传入的小数值字符串转换为一个double类型的浮点数
        Double r = Double.parseDouble(ratio);
        // 当固定的随机数序列rnd <= ratio的值时，返回
        return this.rnd.nextDouble() <= r;
    }

    /**
     * 解析`hw-{1:100}`形式的作业名称 功能：将学生的作业分组管理 
     * (1) 通过正则表达式看学生作业的命名格式是否规范 hw-{1:100} hw-
     * (2) 将作业添加到当前的作业名称列表集合下，也就是将每个学生的每次作业进行打包
     * 
     * @param hwName
     * @return
     */
    // 创建一个编译作业集的列表，传入每次作业的名称作为参数
    static List<String> compileHwList(String hwName) {
        // 创建一个名单列表
        List<String> hwNameList = new ArrayList<>();
        /**
         * Pattern:常规表达的编译型表示 对于一个常规的表达，如果想将它指定为确定的字符串，必须先将其编译为String类的实例
         * 生成的正则表达式可以用于创建匹配对象，将任意的字符序列与常规表达式进行匹配 这样可以支持匹配对象的状态都被存储于匹配器中，支持多个匹配者共享相同的模式
         */
        // 将一个正则表达式编译为pattern对象，调用compile方法返回hw的实例，并规定返回参数格式
        // 在这里只抓取带有通配标识符的“hw-{1:10}”
        Pattern pattern = Pattern.compile("hw-\\{(\\d+):(\\d+)\\}");
        // 使用pattern对象创建Matcher对象，得到一个匹配器，使需要进行匹配的字符序列hwName与给定的正则表达式进行匹配
        // 这里需要matcher只在hwName中抓取那些符合hw-\\{(\\d+):(\\d+)\\}命名规范的索引项
        Matcher matcher = pattern.matcher(hwName);
        // 将传入编译的所有语句序列与匹配器进行匹配解析
        if (matcher.matches()) {
            // 匹配成功后，将上一次与pattern匹配的索引号为1的数字字符串转换成int类型
            int a = Integer.parseInt(matcher.group(1));
            // 匹配成功后，将上一次与pattern匹配的索引号为2的数字字符串转换成int类型
            int b = Integer.parseInt(matcher.group(2));
            // 将每个人的每次作业依据索引号添加到该对应的hwNameList下
            for (int i = a; i <= b; i++) {
                hwNameList.add("hw-" + i);
            }
            // 如果匹配解析失败，就说明不是hw-{1:100}的形式，而是hw-num的形式，直接添加到作业名单中
        } else {
            hwNameList.add(hwName);
        }
        // 返回作业名单
        return hwNameList;
    }

    public static void main(String[] args) {
        // 测试
        // javac -cp src/ -d dist/ src/Doraemon.java
        // java -cp dist/ Doraemon config.txt
        String configFile = "config.txt";
        String hwDir = "hw";
        String hwName = "hw-";
        System.out.println();
        Doraemon doraemon = new Doraemon(configFile, hwDir);
        Map<String, String> comments = doraemon.writeComment(hwName);
    }

}